package jflowmap.views.heatmap;

import java.awt.Color;
import java.awt.geom.Rectangle2D;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import jflowmap.data.ValueStatistics;
import jflowmap.model.graph.Edge;
import jflowmap.model.graph.Graph;
import jflowmap.model.graph.Node;
import jflowmap.model.ontology.Context;
import jflowmap.model.ontology.Dimension;
import jflowmap.model.ontology.SelectionListener;
import jflowmap.model.ontology.Value;
import jflowmap.model.ontology.View;
import jflowmap.util.piccolo.PNodes;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.nodes.PPath;
import edu.umd.cs.piccolo.nodes.PText;
import edu.umd.cs.piccolox.util.PFixedWidthStroke;

public class BarChart extends PNode implements SelectionListener {
	public final static double MAX_HEIGHT = 100;

	private class Bar extends PNode {
		private final Dimension valueDimension;
		private final Map<Dimension, Value> values = Maps.newHashMap();
		private final List<Value> selectedValues = Lists.newLinkedList();

		public Bar(Dimension valueDimension) {
			this.valueDimension = valueDimension;
			createValues();
		}

		public Dimension getValueDimension() {
			return this.valueDimension;
		}

		private void createValues() {
			for (final Edge edge: source.getOutgoingEdges()) {
				final Value value = edge.getValue(valueDimension);
				if (value == null) {
					continue;
				}
				values.put(edge.getTargetNode().getDimension(), value);
			}
		}

		public void select(Dimension dimension) {
			this.selectedValues.add(values.get(dimension));
		}

		public void unselect(Dimension dimension) {
			this.selectedValues.remove(values.get(dimension));
		}

		public void update(double maxBarSum) {
			this.removeAllChildren();

			double y = MAX_HEIGHT;
			for (final Value value: selectedValues) {
				if (value == Context.missingValue) continue;

				final double height = (value.getValue() / maxBarSum) * MAX_HEIGHT;
				final double width = maxWidth / visibleBars;

				final PPath node = new PPath(new Rectangle2D.Double(0, y-height, width, height));

				y -= height;
				node.setPaint(Color.red);
				node.setStroke(new PFixedWidthStroke(1));

				this.addChild(node);
			}
		}

		public ValueStatistics getValueStatistics() {
			return ValueStatistics.createFor(selectedValues);
		}
	}

	private final Node source;
	private final View view;
	private final Graph graph;
	private final Map<Dimension, Bar> bars = Maps.newHashMap();
	private int visibleBars = 0;
	private final double maxWidth;

	public BarChart(Node source, Graph graph, View view, double maxWidth) {
		this.source = source;
		this.view = view;
		this.graph = graph;
		this.maxWidth = maxWidth;

		createBars();
		createSelectionListeners();
	}

	private void createBars() {
		for (final Dimension dimension: graph.getValueDimensions()) {
			final Bar bar = new Bar(dimension);
			bars.put(dimension, bar);
		}
	}

	private final List<SelectionListener> listeners = Lists.newArrayList();

	private void createSelectionListeners() {
		for (final Dimension valueDimension: graph.getValueDimensions()) {
			view.getSelection("standard").addSelectionListener(valueDimension, this);

			if (view.getSelection("standard").isSelected("VALUE", valueDimension)) {
				this.select("VALUE", valueDimension);
			}
		}

		for (final Node targetNode: graph.getTargetNodes()) {
			final SelectionListener listener = new SelectionListener() {
				@Override
				public void select(String category, Dimension dimension) {
					if ("TARGET".equals(category)) {
						for (final Bar bar: bars.values()) {
							bar.select(dimension);
						}
						updateBars();
					}
				}

				@Override
				public void unselect(String category, Dimension dimension) {
					if ("TARGET".equals(category)) {
						for (final Bar bar: bars.values()) {
							bar.unselect(dimension);
						}
						updateBars();
					}
				}

				@Override
				public void dispose() {
					view.getSelection("standard").removeSelectionListener(targetNode.getDimension(), this);
				}
			};

			listeners.add(listener);
			view.getSelection("standard").addSelectionListener(targetNode.getDimension(), listener);
			

			if (view.getSelection("standard").isSelected("TARGET", targetNode.getDimension())) {
				System.out.println("Bar: " + targetNode.getDimension() + " is selected!");
				listener.select("TARGET", targetNode.getDimension());
			}
		}
	}


	@Override
	public void select(String category, Dimension dimension) {
		if ("VALUE".equals(category)) {
			final Bar bar = bars.get(dimension);
			assert(bar != null);
			this.addChild(bar);
			visibleBars++;
			update();
		}
	}


	@Override
	public void unselect(String category, Dimension dimension) {
		if ("VALUE".equals(category)) {
			final Bar bar = bars.get(dimension);
			assert(bar != null);
			this.removeChild(bar);
			visibleBars--;
			update();
		}
	}

	public void updateBars() {
		ValueStatistics stat = null;

		for (final Bar bar: bars.values()) {
			ValueStatistics current = bar.getValueStatistics();
			if (stat == null || current.getSum() > stat.getSum()) {
				stat = current;
			}
		}

		if (stat == null) return;

		for (final Bar bar: bars.values()) {
			bar.update(stat.getSum());
		}

		this.update();
	}

	public void update() {		
		List<Bar> bars = Lists.newLinkedList(PNodes.childrenOfType(this, Bar.class));
		Collections.sort(bars, new Comparator<Bar>() {
			@Override
			public int compare(Bar o1, Bar o2) {
				return Dimension.COMPARE_BY_NAME.compare(o1.getValueDimension(), o2.getValueDimension());
			}
		});

		double x = 0;
		for (Bar bar: bars) {
			bar.setOffset(x, 0);
			x += maxWidth / visibleBars + 2;
		}
	}

	@Override
	public void dispose() {
		for (final Dimension valueDimension: graph.getValueDimensions()) {
			view.getSelection("standard").removeSelectionListener(valueDimension, this);
		}

		for (final SelectionListener listener: this.listeners) {
			listener.dispose();
		}
	}
}
